$.fn.smartWizard   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 26
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 20
nc 2
nop 1
dl 0
loc 26
rs 9.4
c 0
b 0
f 0
1
/*
2
 * SmartWizard 3.3.1 plugin
3
 * jQuery Wizard control Plugin
4
 * by Dipu
5
 *
6
 * Refactored and extended:
7
 * https://github.com/mstratman/jQuery-Smart-Wizard
8
 *
9
 * Original URLs:
10
 * http://www.techlaboratory.net
11
 * http://tech-laboratory.blogspot.com
12
 */
13
14
function SmartWizard(target, options) {
15
    this.target       = target;
16
    this.options      = options;
17
    this.curStepIdx   = options.selected;
18
    this.steps        = $(target).children("ul").children("li").children("a"); // Get all anchors
19
    this.contentWidth = 0;
20
    this.msgBox = $('<div class="msgBox"><div class="content"></div><a href="#" class="close">X</a></div>');
21
    this.elmStepContainer = $('<div></div>').addClass("stepContainer");
22
    this.loader = $('<div>Loading</div>').addClass("loader");
23
    this.buttons = {
24
        next : $('<a>'+options.labelNext+'</a>').attr("href","#").addClass("btn btn-success"),
25
        previous : $('<a>'+options.labelPrevious+'</a>').attr("href","#").addClass("btn btn-primary"),
26
        finish  : $('<a>'+options.labelFinish+'</a>').attr("href","#").addClass("btn btn-default")
27
    };
28
29
    /*
30
     * Private functions
31
     */
32
33
    var _init = function($this) {
34
        var elmActionBar = $('<div></div>').addClass("actionBar");
35
        //elmActionBar.append($this.msgBox);
36
        $('.close',$this.msgBox).click(function() {
37
            $this.msgBox.fadeOut("normal");
38
            return false;
39
        });
40
41
        var allDivs = $this.target.children('div');
42
        // CHeck if ul with steps has been added by user, if not add them
43
        if($this.target.children('ul').length == 0 ){
44
            var ul = $("<ul/>");
45
            target.prepend(ul)
46
47
            // for each div create a li
48
            allDivs.each(function(i,e){
49
                var title = $(e).first().children(".StepTitle").text();
50
                var s = $(e).attr("id")
51
                // if referenced div has no id, add one.
52
                if (s==undefined){
53
                    s = "step-"+(i+1)
54
                    $(e).attr("id",s);
55
                }
56
                var span = $("<span/>").addClass("stepDesc").text(title);
57
                var li = $("<li></li>").append($("<a></a>").attr("href", "#" + s).append($("<label></label>").addClass("stepNumber").text(i + 1)).append(span));
58
                ul.append(li);
59
            });
60
            // (re)initialise the steps property
61
            $this.steps = $(target).children("ul").children("li").children("a"); // Get all anchors
62
        }
63
        $this.target.children('ul').addClass("anchor");
64
        allDivs.addClass("wizard_content");
65
66
        // highlight steps with errors
67
        if($this.options.errorSteps && $this.options.errorSteps.length>0){
68
            $.each($this.options.errorSteps, function(i, n){
69
                $this.setError({ stepnum: n, iserror:true });
70
            });
71
        }
72
73
        $this.elmStepContainer.append(allDivs);
74
        //elmActionBar.append($this.loader);
75
        $this.target.append($this.elmStepContainer);
76
77
        if ($this.options.includeFinishButton){
78
            elmActionBar.append($this.buttons.finish)
79
        }
80
81
        elmActionBar.append($this.buttons.next)
82
            .append($this.buttons.previous);
83
        $this.target.append(elmActionBar);
84
        this.contentWidth = $this.elmStepContainer.width();
85
86
        $($this.buttons.next).click(function() {
87
            $this.goForward();
88
            return false;
89
        });
90
        $($this.buttons.previous).click(function() {
91
            $this.goBackward();
92
            return false;
93
        });
94
        $($this.buttons.finish).click(function() {
95
            if(!$(this).hasClass('buttonDisabled')){
96
                if($.isFunction($this.options.onFinish)) {
97
                    var context = { fromStep: $this.curStepIdx + 1 };
98
                    if(!$this.options.onFinish.call(this,$($this.steps), context)){
99
                        return false;
100
                    }
101
                }else{
102
                    var frm = $this.target.parents('form');
103
                    if(frm && frm.length){
104
                        frm.submit();
105
                    }
106
                }
107
            }
108
            return false;
109
        });
110
111
        $($this.steps).bind("click", function(e){
0 ignored issues
show
Unused Code introduced by
The parameter e is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
112
            if($this.steps.index(this) == $this.curStepIdx){
113
                return false;
114
            }
115
            var nextStepIdx = $this.steps.index(this);
116
            var isDone = $this.steps.eq(nextStepIdx).attr("isDone") - 0;
117
            if(isDone == 1){
118
                _loadContent($this, nextStepIdx);
119
            }
120
            return false;
121
        });
122
123
        // Enable keyboard navigation
124
        if($this.options.keyNavigation){
125
            $(document).keyup(function(e){
126
                if(e.which==39){ // Right Arrow
127
                    $this.goForward();
128
                }else if(e.which==37){ // Left Arrow
129
                    $this.goBackward();
130
                }
131
            });
132
        }
133
        //  Prepare the steps
134
        _prepareSteps($this);
135
        // Show the first slected step
136
        _loadContent($this, $this.curStepIdx);
137
    };
138
139
    var _prepareSteps = function($this) {
140
        if(! $this.options.enableAllSteps){
141
            $($this.steps, $this.target).removeClass("selected").removeClass("done").addClass("disabled");
142
            $($this.steps, $this.target).attr("isDone",0);
143
        }else{
144
            $($this.steps, $this.target).removeClass("selected").removeClass("disabled").addClass("done");
145
            $($this.steps, $this.target).attr("isDone",1);
146
        }
147
148
        $($this.steps, $this.target).each(function(i){
149
            $($(this).attr("href").replace(/^.+#/, '#'), $this.target).hide();
150
            $(this).attr("rel",i+1);
151
        });
152
    };
153
154
    var _step = function ($this, selStep) {
155
        return $(
156
            $(selStep, $this.target).attr("href").replace(/^.+#/, '#'),
157
            $this.target
158
        );
159
    };
160
161
    var _loadContent = function($this, stepIdx) {
162
        var selStep = $this.steps.eq(stepIdx);
163
        var ajaxurl = $this.options.contentURL;
164
        var ajaxurl_data = $this.options.contentURLData;
165
        var hasContent = selStep.data('hasContent');
166
        var stepNum = stepIdx+1;
167
        if (ajaxurl && ajaxurl.length>0) {
168
            if ($this.options.contentCache && hasContent) {
169
                _showStep($this, stepIdx);
170
            } else {
171
                var ajax_args = {
172
                    url: ajaxurl,
173
                    type: $this.options.ajaxType,
174
                    data: ({step_number : stepNum}),
175
                    dataType: "text",
176
                    beforeSend: function(){
177
                        $this.loader.show();
178
                    },
179
                    error: function(){
180
                        $this.loader.hide();
181
                    },
182
                    success: function(res){
183
                        $this.loader.hide();
184
                        if(res && res.length>0){
185
                            selStep.data('hasContent',true);
186
                            _step($this, selStep).html(res);
187
                            _showStep($this, stepIdx);
188
                        }
189
                    }
190
                };
191
                if (ajaxurl_data) {
192
                    ajax_args = $.extend(ajax_args, ajaxurl_data(stepNum));
193
                }
194
                $.ajax(ajax_args);
195
            }
196
        }else{
197
            _showStep($this,stepIdx);
198
        }
199
    };
200
201
    var _showStep = function($this, stepIdx) {
202
        var selStep = $this.steps.eq(stepIdx);
203
        var curStep = $this.steps.eq($this.curStepIdx);
204
        if(stepIdx != $this.curStepIdx){
205
            if($.isFunction($this.options.onLeaveStep)) {
206
                var context = { fromStep: $this.curStepIdx+1, toStep: stepIdx+1 };
207
                if (! $this.options.onLeaveStep.call($this,$(curStep), context)){
208
                    return false;
209
                }
210
            }
211
        }
212
        //$this.elmStepContainer.height(_step($this, selStep).outerHeight());
213
        var prevCurStepIdx = $this.curStepIdx;
214
        $this.curStepIdx =  stepIdx;
215
        if ($this.options.transitionEffect == 'slide'){
216
            _step($this, curStep).slideUp("fast",function(e){
0 ignored issues
show
Unused Code introduced by
The parameter e is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
217
                _step($this, selStep).slideDown("fast");
218
                _setupStep($this,curStep,selStep);
219
            });
220
        } else if ($this.options.transitionEffect == 'fade'){
221
            _step($this, curStep).fadeOut("fast",function(e){
0 ignored issues
show
Unused Code introduced by
The parameter e is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
222
                _step($this, selStep).fadeIn("fast");
223
                _setupStep($this,curStep,selStep);
224
            });
225
        } else if ($this.options.transitionEffect == 'slideleft'){
226
            var nextElmLeft = 0;
227
            var nextElmLeft1 = null;
0 ignored issues
show
Unused Code introduced by
The assignment to nextElmLeft1 seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
228
            var nextElmLeft = null;
0 ignored issues
show
Unused Code introduced by
The variable nextElmLeft seems to be never used. Consider removing it.
Loading history...
Comprehensibility Naming Best Practice introduced by
The variable nextElmLeft already seems to be declared on line 226. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
229
            var curElementLeft = 0;
230
            if(stepIdx > prevCurStepIdx){
231
                nextElmLeft1 = $this.elmStepContainer.width() + 10;
232
                nextElmLeft2 = 0;
0 ignored issues
show
Bug introduced by
The variable nextElmLeft2 seems to be never declared. Assigning variables without defining them first makes them global. If this was intended, consider making it explicit like using window.nextElmLeft2.
Loading history...
233
                curElementLeft = 0 - _step($this, curStep).outerWidth();
234
            } else {
235
                nextElmLeft1 = 0 - _step($this, selStep).outerWidth() + 20;
236
                nextElmLeft2 = 0;
237
                curElementLeft = 10 + _step($this, curStep).outerWidth();
238
            }
239
            if (stepIdx == prevCurStepIdx) {
240
                nextElmLeft1 = $($(selStep, $this.target).attr("href"), $this.target).outerWidth() + 20;
241
                nextElmLeft2 = 0;
242
                curElementLeft = 0 - $($(curStep, $this.target).attr("href"), $this.target).outerWidth();
0 ignored issues
show
Unused Code introduced by
The assignment to variable curElementLeft seems to be never used. Consider removing it.
Loading history...
243
            } else {
244
                $($(curStep, $this.target).attr("href"), $this.target).animate({left:curElementLeft},"fast",function(e){
0 ignored issues
show
Unused Code introduced by
The parameter e is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
245
                    $($(curStep, $this.target).attr("href"), $this.target).hide();
246
                });
247
            }
248
249
            _step($this, selStep).css("left",nextElmLeft1).show().animate({left:nextElmLeft2},"fast",function(e){
0 ignored issues
show
Unused Code introduced by
The parameter e is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
250
                _setupStep($this,curStep,selStep);
251
            });
252
        } else {
253
            _step($this, curStep).hide();
254
            _step($this, selStep).show();
255
            _setupStep($this,curStep,selStep);
256
        }
257
        return true;
258
    };
259
260
    var _setupStep = function($this, curStep, selStep) {
261
        $(curStep, $this.target).removeClass("selected");
262
        $(curStep, $this.target).addClass("done");
263
264
        $(selStep, $this.target).removeClass("disabled");
265
        $(selStep, $this.target).removeClass("done");
266
        $(selStep, $this.target).addClass("selected");
267
268
        $(selStep, $this.target).attr("isDone",1);
269
270
        _adjustButton($this);
271
272
        if($.isFunction($this.options.onShowStep)) {
273
            var context = { fromStep: parseInt($(curStep).attr('rel')), toStep: parseInt($(selStep).attr('rel')) };
274
            if(! $this.options.onShowStep.call(this,$(selStep),context)){
275
                return false;
276
            }
277
        }
278
        if ($this.options.noForwardJumping) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if $this.options.noForwardJumping is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
279
            // +2 == +1 (for index to step num) +1 (for next step)
280
            for (var i = $this.curStepIdx + 2; i <= $this.steps.length; i++) {
281
                $this.disableStep(i);
282
            }
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
283
        }
284
    };
285
286
    var _adjustButton = function($this) {
287
        if (! $this.options.cycleSteps){
288
            if (0 >= $this.curStepIdx) {
289
                $($this.buttons.previous).addClass("buttonDisabled");
290
                if ($this.options.hideButtonsOnDisabled) {
291
                    $($this.buttons.previous).hide();
292
                }
293
            }else{
294
                $($this.buttons.previous).removeClass("buttonDisabled");
295
                if ($this.options.hideButtonsOnDisabled) {
296
                    $($this.buttons.previous).show();
297
                }
298
            }
299
            if (($this.steps.length-1) <= $this.curStepIdx){
300
                $($this.buttons.next).addClass("buttonDisabled");
301
                if ($this.options.hideButtonsOnDisabled) {
302
                    $($this.buttons.next).hide();
303
                }
304
            }else{
305
                $($this.buttons.next).removeClass("buttonDisabled");
306
                if ($this.options.hideButtonsOnDisabled) {
307
                    $($this.buttons.next).show();
308
                }
309
            }
310
        }
311
        // Finish Button
312
        $this.enableFinish($this.options.enableFinishButton);
313
    };
314
315
    /*
316
     * Public methods
317
     */
318
319
    SmartWizard.prototype.goForward = function(){
320
        var nextStepIdx = this.curStepIdx + 1;
321
        if (this.steps.length <= nextStepIdx){
322
            if (! this.options.cycleSteps){
323
                return false;
324
            }
325
            nextStepIdx = 0;
326
        }
327
        _loadContent(this, nextStepIdx);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
328
    };
329
330
    SmartWizard.prototype.goBackward = function(){
331
        var nextStepIdx = this.curStepIdx-1;
332
        if (0 > nextStepIdx){
333
            if (! this.options.cycleSteps){
334
                return false;
335
            }
336
            nextStepIdx = this.steps.length - 1;
337
        }
338
        _loadContent(this, nextStepIdx);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
339
    };
340
341
    SmartWizard.prototype.goToStep = function(stepNum){
342
        var stepIdx = stepNum - 1;
343
        if (stepIdx >= 0 && stepIdx < this.steps.length) {
344
            _loadContent(this, stepIdx);
345
        }
346
    };
347
    SmartWizard.prototype.enableStep = function(stepNum) {
348
        var stepIdx = stepNum - 1;
349
        if (stepIdx == this.curStepIdx || stepIdx < 0 || stepIdx >= this.steps.length) {
350
            return false;
351
        }
352
        var step = this.steps.eq(stepIdx);
353
        $(step, this.target).attr("isDone",1);
354
        $(step, this.target).removeClass("disabled").removeClass("selected").addClass("done");
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
355
    }
356
    SmartWizard.prototype.disableStep = function(stepNum) {
357
        var stepIdx = stepNum - 1;
358
        if (stepIdx == this.curStepIdx || stepIdx < 0 || stepIdx >= this.steps.length) {
359
            return false;
360
        }
361
        var step = this.steps.eq(stepIdx);
362
        $(step, this.target).attr("isDone",0);
363
        $(step, this.target).removeClass("done").removeClass("selected").addClass("disabled");
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
364
    }
365
    SmartWizard.prototype.currentStep = function() {
366
        return this.curStepIdx + 1;
367
    }
368
369
    SmartWizard.prototype.showMessage = function (msg) {
370
        $('.content', this.msgBox).html(msg);
371
        this.msgBox.show();
372
    }
373
374
    SmartWizard.prototype.enableFinish = function (enable) {
375
        // Controll status of finish button dynamically
376
        // just call this with status you want
377
        this.options.enableFinishButton = enable;
378
        if (this.options.includeFinishButton){
379
            if (!this.steps.hasClass('disabled') || this.options.enableFinishButton){
380
                $(this.buttons.finish).removeClass("buttonDisabled");
381
                if (this.options.hideButtonsOnDisabled) {
382
                    $(this.buttons.finish).show();
383
                }
384
            }else{
385
                $(this.buttons.finish).addClass("buttonDisabled");
386
                if (this.options.hideButtonsOnDisabled) {
387
                    $(this.buttons.finish).hide();
388
                }
389
            }
390
        }
391
        return this.options.enableFinishButton;
392
    }
393
394
    SmartWizard.prototype.hideMessage = function () {
395
        this.msgBox.fadeOut("normal");
396
    }
397
    SmartWizard.prototype.showError = function(stepnum) {
398
        this.setError(stepnum, true);
399
    }
400
    SmartWizard.prototype.hideError = function(stepnum) {
401
        this.setError(stepnum, false);
402
    }
403
    SmartWizard.prototype.setError = function(stepnum,iserror) {
404
        if (typeof stepnum == "object") {
405
            iserror = stepnum.iserror;
406
            stepnum = stepnum.stepnum;
407
        }
408
409
        if (iserror){
410
            $(this.steps.eq(stepnum-1), this.target).addClass('error')
411
        }else{
412
            $(this.steps.eq(stepnum-1), this.target).removeClass("error");
413
        }
414
    }
415
416
    SmartWizard.prototype.fixHeight = function(){
417
        var height = 0;
418
419
        var selStep = this.steps.eq(this.curStepIdx);
420
        var stepContainer = _step(this, selStep);
421
        stepContainer.children().each(function() {
422
            if($(this).is(':visible')) {
423
                 height += $(this).outerHeight(true);
424
            }
425
        });
426
427
        // These values (5 and 20) are experimentally chosen.
428
        stepContainer.height(height + 5);
429
        this.elmStepContainer.height(height + 20); 
430
   }
431
432
    _init(this);
433
};
434
435
436
437
(function($){
438
439
    $.fn.smartWizard = function(method) {
440
        var args = arguments;
441
        var rv = undefined;
0 ignored issues
show
Unused Code Comprehensibility introduced by
The assignment of undefined is not necessary as rv is implicitly marked as undefined by the declaration.
Loading history...
442
        var allObjs = this.each(function() {
443
            var wiz = $(this).data('smartWizard');
444
            if (typeof method == 'object' || ! method || ! wiz) {
445
                var options = $.extend({}, $.fn.smartWizard.defaults, method || {});
446
                if (! wiz) {
0 ignored issues
show
Complexity Best Practice introduced by
There is no return statement if !wiz is false. Are you sure this is correct? If so, consider adding return; explicitly.

This check looks for functions where a return statement is found in some execution paths, but not in all.

Consider this little piece of code

function isBig(a) {
    if (a > 5000) {
        return "yes";
    }
}

console.log(isBig(5001)); //returns yes
console.log(isBig(42)); //returns undefined

The function isBig will only return a specific value when its parameter is bigger than 5000. In any other case, it will implicitly return undefined.

This behaviour may not be what you had intended. In any case, you can add a return undefined to the other execution path to make the return value explicit.

Loading history...
447
                    wiz = new SmartWizard($(this), options);
448
                    $(this).data('smartWizard', wiz);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
449
                }
450
            } else {
451
                if (typeof SmartWizard.prototype[method] == "function") {
452
                    rv = SmartWizard.prototype[method].apply(wiz, Array.prototype.slice.call(args, 1));
453
                    return rv;
454
                } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
455
                    $.error('Method ' + method + ' does not exist on jQuery.smartWizard');
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
456
                }
457
            }
458
        });
459
        if (rv === undefined) {
460
            return allObjs;
461
        } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
462
            return rv;
463
        }
464
    };
465
466
// Default Properties and Events
467
    $.fn.smartWizard.defaults = {
468
        selected: 0,  // Selected Step, 0 = first step
469
        keyNavigation: true, // Enable/Disable key navigation(left and right keys are used if enabled)
470
        enableAllSteps: false,
471
        transitionEffect: 'fade', // Effect on navigation, none/fade/slide/slideleft
472
        contentURL:null, // content url, Enables Ajax content loading
473
        contentCache:true, // cache step contents, if false content is fetched always from ajax url
474
        cycleSteps: false, // cycle step navigation
475
        enableFinishButton: false, // make finish button enabled always
476
        hideButtonsOnDisabled: false, // when the previous/next/finish buttons are disabled, hide them instead?
477
        errorSteps:[],    // Array Steps with errors
478
        labelNext:'Next',
479
        labelPrevious:'Previous',
480
        labelFinish:'Finish',
481
        noForwardJumping: false,
482
        ajaxType: "POST",
483
        onLeaveStep: null, // triggers when leaving a step
484
        onShowStep: null,  // triggers when showing a step
485
        onFinish: null,  // triggers when Finish button is clicked
486
        includeFinishButton : true   // Add the finish button
487
    };
488
489
})(jQuery);
490